package com.geekhole.demo.user.application.v2.service.base;

import com.geekhole.demo.user.domain.Agg;
import com.geekhole.demo.user.domain.DomainServiceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

abstract class AbstractDomainService<AGG extends Agg, CONTEXT extends DomainServiceContext> {
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDomainService.class);

    private final ApplicationEventPublisher eventPublisher;

    private final CrudRepository<AGG, ?> repository;

    public AbstractDomainService(CrudRepository<AGG, ?> repository,
                                 ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
        this.repository = repository;
    }

    @Transactional(readOnly = false)
    public void handle(CONTEXT context){
        try {
            // 回调子类接口，用于扩展
            AGG agg = doHandle(context);

            // 将变更持久化到 DB
            save2DB(agg);

            // 发布领域事件，完成后对事件进行清理
            publishAndCleanEvent(agg);

            // 成功回调，默认打印日志
            onSuccess(agg, context);
        }catch (Exception e){
            // 异常处理，直接中断流程
            onException(e, context);
        }
    }

    /**
     * 回调接口，用于子类进行扩展
     * @param context
     * @return
     */
    protected abstract AGG doHandle(CONTEXT context);

    /**
     * 异常默认处理策略，子类通过重新可以进行自定义
     * @param e
     * @param context
     */
    protected void onException(Exception e, CONTEXT context){
        LOGGER.error("failed to handle {}", context, e);
        if (e instanceof RuntimeException){
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }

    /**
     * 默认成功回调，子类通过重写可以进行自定义
     * @param agg
     * @param context
     */
    protected void onSuccess(AGG agg, CONTEXT context){
        LOGGER.info("success to handle {} and sync {} to DB", context, agg);
    }

    /**
     * 发布并清理领域事件
     * @param agg
     */
    private void publishAndCleanEvent(AGG agg){
        // 1. 发布领域事件
        agg.foreachEvent(this.eventPublisher::publishEvent);

        // 2. 清理领域事件
        agg.clearEvents();
    }

    /**
     * 将变更持久化到 DB 中
     * @param agg
     */
    private void save2DB(AGG agg){
        this.repository.save(agg);
    }
}
